package controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

import swap.ConfirmSwapMessage;
import swap.MatchSwapMessage;
import swap.Swap;
import util.IProtocol;
import util.Util;
import broker.BrokerClient;
import broker.IHandleBrokerMessage;
import broker.ReaderThread;
import entity.Board;
import entity.Word;
import entity.WordType;
import gui.BoardApplication;

/**
 * This is the swap request controller. 
 */
public class SwapRequestController implements IHandleBrokerMessage {
	public Board board;
	private BoardApplication gui;
	public BrokerClient broker;
	public ReaderThread readerThread;

	private ArrayList<JComboBox> outboundTypeCombos;
	private ArrayList<JComboBox> outboundWordCombos;
	private ArrayList<JComboBox> inboundTypeCombos;
	private ArrayList<JTextField> inboundWordFields;

	private ArrayList<WordType> outboundWordTypes;
	private ArrayList<Word> outboundWords;
	private ArrayList<WordType> inboundWordTypes;
	
	private boolean waitingSwap;
	private int selectedWordsCount;
	
	/**
	 * Constructor of swap request controller.
	 */
	public SwapRequestController(BoardApplication gui, Board board){
		this.board = board;
		this.gui = gui;

		connectToBroker();
		
		outboundWords = new ArrayList<Word>();
		outboundWordTypes = new ArrayList<WordType>();
		inboundWordTypes = new ArrayList<WordType>();
		
		waitingSwap = false;
		selectedWordsCount = 0;
	}
	
	/**
	 * this method receives the types selected from the drop down 
	 * in the gui for the offered words 
	 * @param index
	 */
	public void selectOutboundWordType(int index){
		if(outboundTypeCombos.get(index) != null
		&& outboundTypeCombos.get(index).getSelectedItem() != null
		&& outboundTypeCombos.get(index).getSelectedIndex() > 0){
			WordType wt = Util.parseWordType(outboundTypeCombos.get(index).getSelectedItem().toString().toUpperCase());
			ArrayList<Word> words = board.getUnprotectedWordsByType(wt);

			for(JComboBox cb : outboundWordCombos){
				words.remove((Word) cb.getSelectedItem());
			}
			
			if(words.size() > 0){
				words.add(0, new Word(-1, "* (Any)", null));
			}else{
				JOptionPane.showMessageDialog(null, "There are no words for the \"Type\" you selected", "Error", JOptionPane.ERROR_MESSAGE);
				outboundTypeCombos.get(index).setSelectedIndex(0);
			}
			
			outboundWordTypes.set(index, wt);
			outboundWordCombos.get(index).setModel(new DefaultComboBoxModel(words.toArray()));
		}
		
		updateEnabledFields();
	}
	/**
	 * this method accepts an index to the array of words we request in a swap
	 * @param index
	 */
	public void selectInboundWordType(int index){
		if(inboundTypeCombos.get(index) != null
		&& inboundTypeCombos.get(index).getSelectedItem() != null
		&& inboundTypeCombos.get(index).getSelectedIndex() > 0){
			WordType wt = Util.parseWordType(inboundTypeCombos.get(index).getSelectedItem().toString().toUpperCase());
			inboundWordTypes.set(index, wt);
		}
	}
	/**
	 * the method takes the index of the work selected from the drop down for swap
	 * 
	 * @param index
	 */
	public void selectOutboundWord(int index){
		if(outboundWordCombos.get(index) != null
		&& outboundWordCombos.get(index).getSelectedItem() != null
		&& outboundWordCombos.get(index).getSelectedIndex() > 0){
			Word w = (Word) outboundWordCombos.get(index).getSelectedItem();
			outboundWords.set(index, w);
		}
	}
	
	/**
	 * this method enables the send button for swap if 
	 * there are out bound words 
	 */
	public void updateEnabledFields(){
		selectedWordsCount = 0;
		
		for(int i = 0; i < 5; i++){
			if(outboundTypeCombos.get(i).getSelectedIndex() != 0){
				selectedWordsCount = i + 1;
			}else{
				break;
			}
		}
		
		if(selectedWordsCount > 0){
			gui.swapSendButton.setEnabled(true);
		}else{
			gui.swapSendButton.setEnabled(false);
		}
		
		for(int i = 0; i < 5; i++){
			if(i < selectedWordsCount){
				outboundTypeCombos.get(i).setEnabled(true);
				outboundWordCombos.get(i).setEnabled(true);
				inboundTypeCombos.get(i).setEnabled(true);
				inboundWordFields.get(i).setEnabled(true);
			}else if(i == selectedWordsCount){
				outboundTypeCombos.get(i).setEnabled(true);
				outboundWordCombos.get(i).setEnabled(false);
				inboundTypeCombos.get(i).setEnabled(false);
				inboundWordFields.get(i).setEnabled(false);
				
				outboundWordCombos.get(i).setSelectedIndex(-1);
				inboundTypeCombos.get(i).setSelectedIndex(0);
				inboundWordFields.get(i).setText("");
			}else{
				outboundTypeCombos.get(i).setEnabled(false);
				outboundWordCombos.get(i).setEnabled(false);
				inboundTypeCombos.get(i).setEnabled(false);
				inboundWordFields.get(i).setEnabled(false);

				outboundTypeCombos.get(i).setSelectedIndex(0);
				outboundWordCombos.get(i).setSelectedIndex(-1);
				inboundTypeCombos.get(i).setSelectedIndex(0);
				inboundWordFields.get(i).setText("");
			}
		}
	}
	
	public void cleanOutboundArea(){
		outboundWords = new ArrayList<Word>();
		outboundWordTypes = new ArrayList<WordType>();
		inboundWordTypes = new ArrayList<WordType>();
		
		selectedWordsCount = 0;

		for(int i = 0; i < 5; i++){
			outboundWords.add(null);
			outboundWordTypes.add(null);
			inboundWordTypes.add(null);
			
			outboundTypeCombos.get(i).setSelectedIndex(0);
			outboundWordCombos.get(i).setSelectedIndex(-1);
			inboundTypeCombos.get(i).setSelectedIndex(0);
			inboundWordFields.get(i).setText("");
		}
		
		updateEnabledFields();
	}
	
	public void cleanInboundArea(){
		inboundWordTypes = new ArrayList<WordType>();

		for(int i = 0; i < 5; i++){
			inboundTypeCombos.get(i).setSelectedIndex(0);
			inboundWordFields.get(i).setText("");
		}
		
		updateEnabledFields();
	}
	
	public void updateComboBoxes(){
		outboundTypeCombos = new ArrayList<JComboBox>();
		outboundTypeCombos.add(gui.outboundTypeCombo1);
		outboundTypeCombos.add(gui.outboundTypeCombo2);
		outboundTypeCombos.add(gui.outboundTypeCombo3);
		outboundTypeCombos.add(gui.outboundTypeCombo4);
		outboundTypeCombos.add(gui.outboundTypeCombo5);

		outboundWordCombos = new ArrayList<JComboBox>();
		outboundWordCombos.add(gui.outboundWordCombo1);
		outboundWordCombos.add(gui.outboundWordCombo2);
		outboundWordCombos.add(gui.outboundWordCombo3);
		outboundWordCombos.add(gui.outboundWordCombo4);
		outboundWordCombos.add(gui.outboundWordCombo5);
		
		inboundTypeCombos = new ArrayList<JComboBox>();
		inboundTypeCombos.add(gui.inboundTypeCombo1);
		inboundTypeCombos.add(gui.inboundTypeCombo2);
		inboundTypeCombos.add(gui.inboundTypeCombo3);
		inboundTypeCombos.add(gui.inboundTypeCombo4);
		inboundTypeCombos.add(gui.inboundTypeCombo5);

		inboundWordFields = new ArrayList<JTextField>();
		inboundWordFields.add(gui.inboundWordField1);
		inboundWordFields.add(gui.inboundWordField2);
		inboundWordFields.add(gui.inboundWordField3);
		inboundWordFields.add(gui.inboundWordField4);
		inboundWordFields.add(gui.inboundWordField5);
		
		for(final JComboBox cb : outboundTypeCombos){
			cb.addActionListener (new ActionListener() {
			    public void actionPerformed(ActionEvent e) {
					selectOutboundWordType(outboundTypeCombos.indexOf(cb));
			    }
			});
		}
		
		for(final JComboBox cb : outboundWordCombos){
			cb.addActionListener (new ActionListener() {
			    public void actionPerformed(ActionEvent e) {
					selectOutboundWord(outboundWordCombos.indexOf(cb));
			    }
			});
		}
		
		for(final JComboBox cb : inboundTypeCombos){
			cb.addActionListener (new ActionListener() {
			    public void actionPerformed(ActionEvent e) {
					selectInboundWordType(inboundTypeCombos.indexOf(cb));
			    }
			});
		}
		
		gui.outboundCleanButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				cleanOutboundArea();
			}
		});
		
		gui.inboundCleanButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				cleanInboundArea();
			}
		});
		
		gui.swapSendButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if(!waitingSwap){
					sendSwapRequest();
					
					gui.swapSendButton.setText("Cancel");
				}else{
					setSwapEnabled(true);
					
					waitingSwap = false;
					gui.swapSendButton.setText("Send");
				}
			}
		});
	}
	
	public void showProtectError(){
		JOptionPane.showMessageDialog(null, "You can't protect a word that has been selected for Swap Request.", "Error", JOptionPane.ERROR_MESSAGE);
	}
	
	public boolean isSelected(Word word){
		boolean result = false;
		
		for(JComboBox cb : outboundWordCombos){
			result = result || word.equals((Word) cb.getSelectedItem());
		}
		
		return result;
	}
	/**
	 * this method connects to broker using the broker client
	 */
	public void connectToBroker(){
		broker = new BrokerClient("gheineman.cs.wpi.edu", 9172);
		
		if (!broker.connect()) {
			System.err.println("unable to connect to broker");
			broker.shutdown();
		}
		
		readerThread = new ReaderThread(broker, this);
		readerThread.start();
	}
	
	public void brokerGone() {
		waitingSwap = false;
		gui.swapSendButton.setText("Cancel");
		
		cleanInboundArea();
		cleanOutboundArea();
		setSwapEnabled(true);
		
		System.out.println("Broker Gone. Trying to reconnect.");
		
		connectToBroker();
	}
	
	public void setSwapEnabled(boolean enabled){
		if(enabled){			
			updateEnabledFields();
			
			gui.outboundCleanButton.setEnabled(true);
			gui.inboundCleanButton.setEnabled(true);
		}else{
			selectedWordsCount = 0;
			
			for(int i = 0; i < 5; i++){
				if(outboundTypeCombos.get(i).getSelectedIndex() != 0){
					selectedWordsCount = i + 1;
				}else{
					break;
				}
			}
			
			for(int i = 0; i < 5; i++){
				outboundTypeCombos.get(i).setEnabled(false);
				outboundWordCombos.get(i).setEnabled(false);
				inboundTypeCombos.get(i).setEnabled(false);
				inboundWordFields.get(i).setEnabled(false);
			}

			gui.outboundCleanButton.setEnabled(false);
			gui.inboundCleanButton.setEnabled(false);
		}
	}
	
	public void selectRandomFields(){
		for(int i = 0; i < 5; i++){
			if(outboundWordCombos.get(i).isEnabled() && outboundWordCombos.get(i).getSelectedIndex() == 0){
				int index = (int) ((Math.random() * (outboundWordCombos.get(i).getItemCount() - 1)) + 1);
				outboundWordCombos.get(i).setSelectedIndex(index);
			}
		}
	}
	
	public String makeSwapRequest(){
		String result = "";
		
		ArrayList<String> outboundTypes = new ArrayList<String>();
		ArrayList<String> outboundValues = new ArrayList<String>();
		ArrayList<String> inboundTypes = new ArrayList<String>();
		ArrayList<String> inboundValues = new ArrayList<String>();
		
		for(Word w : outboundWords){
			if(w != null){
				outboundTypes.add(Util.wordTypeString(w.getWordType()).toLowerCase());
				outboundValues.add(w.getValue());
			}
		}
		
		if(outboundTypes.size() > 0){
			result += outboundTypes.size();
		}else{
			return null;
		}
		
		for(int i = 0; i < outboundValues.size(); i++){
			inboundTypes.add(inboundWordTypes.get(i) != null ? Util.wordTypeString(inboundWordTypes.get(i)).toLowerCase() : "*");
			inboundValues.add(inboundWordFields.get(i).getText().trim() != null && !inboundWordFields.get(i).getText().trim().equals("") ? inboundWordFields.get(i).getText().trim() : "*");
		}
		
		result += IProtocol.separator;
		boolean first = true;
		
		for(String s : outboundTypes){
			result += first ? "" : IProtocol.separator;
			result += s;
			first = false;
		}
		
		result += IProtocol.separator;
		first = true;
		for(String s : outboundValues){
			result += first ? "" : IProtocol.separator;
			result += s;
			first = false;
		}

		result += IProtocol.separator;
		first = true;
		for(String s : inboundTypes){
			result += first ? "" : IProtocol.separator;
			result += s;
			first = false;
		}

		result += IProtocol.separator;
		first = true;
		for(String s : inboundValues){
			result += first ? "" : IProtocol.separator;
			result += s;
			first = false;
		}
		
		return result;
	}
	
	public void sendSwapRequest(){
		selectRandomFields();
		String swapString = makeSwapRequest();
		
		if(swapString == null){
			return;
		}
		
		readerThread.sendMessage(IProtocol.requestSwapMsg + IProtocol.separator +
							broker.getID() + IProtocol.separator + "*" + IProtocol.separator + 
							swapString + IProtocol.separator);
		
		System.out.println("Sending Swap Request: " + swapString);
		
		waitingSwap = true;
		setSwapEnabled(false);
	}
	
	public void process(BrokerClient broker, String msg) {
		System.out.println("Processing Message from Broker: " + msg);
		
		if (msg.startsWith(IProtocol.denySwapMsg)) {
			System.out.println("Denied swap request");
			JOptionPane.showMessageDialog (null, "Your swap request was denied.", "Error", JOptionPane.OK_OPTION);
			
			waitingSwap = false;
			gui.swapSendButton.setText("Send");

			setSwapEnabled(true);
			
			return;
		}
		
		if (msg.startsWith(IProtocol.matchSwapMsg)) {
			Swap s = MatchSwapMessage.getSwap(msg);
			
			ArrayList<Word> tempRemove = new ArrayList<Word>();
			boolean failed = false;
			
			for (int i = 0; i < s.requestWords.length; i++) {
				String value = s.requestWords[i];
				String type = s.requestTypes[i];
				
				ArrayList<Word> words = board.getUnprotectedWordsByValue(value);
				
				if(type != null && !type.equals("*")){
					words = board.getUnprotectedWordsByValueAndType(Util.parseWordType(type), value);
				}
				
				if (words.size() > 0) {
					Word w = words.get(0);
					tempRemove.add(w);
					
					board.getUnprotectedWords().remove(w);
				}else if(value.equals("*")){
					int index = (int) Math.floor(Math.random() * board.getUnprotectedWords().size());
					Word w = board.getUnprotectedWords().get(index);
					tempRemove.add(w);

					board.getUnprotectedWords().remove(w);
				}else{
					failed = true;
					break;
				}
			}
			
			// remove only happens with confirmSwap
			board.getUnprotectedWords().addAll(tempRemove);
			
			if (failed) {
				System.out.println("Unable to satisfy swap request. Sending Deny Swap message: " + IProtocol.denySwapMsg + IProtocol.separator + s.requestor_id);
				return;
			}
			
			String swapString = IProtocol.confirmSwapMsg +
					IProtocol.separator + s.requestor_id +
					IProtocol.separator + s.acceptor_id +
					IProtocol.separator + s.n;

			String offerTypes = "";
			String offerWords = "";
			String requestTypes = "";
			String requestWords = "";
			boolean first = true;
			
			for(int i = 0; i < s.n; i++){
				String separator = first ? "" : IProtocol.separator;
				
				requestTypes += separator + Util.wordTypeString(tempRemove.get(i).getWordType()).toLowerCase();
				requestWords += separator + tempRemove.get(i).getValue().toLowerCase();
				offerTypes += separator + s.offerTypes[i];
				offerWords += separator + s.offerWords[i];
			}
			
			swapString += IProtocol.separator + offerTypes +
					IProtocol.separator + offerWords +
					IProtocol.separator + requestTypes +
					IProtocol.separator + requestWords;
			
			System.out.println("Accepting satisfy swap request. Sending Confirm Swap message: " + swapString);
	
			broker.getBrokerOutput().println(swapString);
			
			return;
		}
		
		if (msg.startsWith(IProtocol.confirmSwapMsg)) {
			Swap s = ConfirmSwapMessage.getSwap(msg);
			
			for(int i = 0; i < s.n; i++){
				if(s.offerWords[i].equals("*")
				|| s.offerTypes[i].equals("*")
				|| s.requestWords[i].equals("*")
				|| s.requestWords[i].equals("*")){
					System.out.println("Malformed Confirm Swap message syntax from client " + s.requestor_id);
				}
			}
			
			// carry out the swap.
			if (broker.getID().equals(s.requestor_id)) {
				if(waitingSwap){
					// We made the request: remove offer words and take request words 
					for (int i = 0; i < s.n; i++) {
						String value = s.offerWords[i];
						String type = s.offerTypes[i];
						
						ArrayList<Word> words = null;
						
						if(value.equals("*")){
							if(type.equals("*")){
								words = board.getUnprotectedWords();
							}else{
								words = board.getUnprotectedWordsByType(Util.parseWordType(type));
							}
						}else{
							if(type.equals("*")){
								words = board.getUnprotectedWordsByValue(value);
							}else{
								words = board.getUnprotectedWordsByValueAndType(Util.parseWordType(type), value);
							}
						}
						
						int index = (int) Math.floor(Math.random() * words.size());
						Word w = words.get(index);

						board.getUnprotectedWords().remove(w);
					}
					
					for (int i = 0; i < s.n; i++) {
						Word w = new Word(Word.nextId(), s.requestWords[i], Util.parseWordType(s.requestTypes[i]));
						
						board.getUnprotectedWords().add(w);
					}
					
					System.out.println("Completed Swap Request successfully");
					
					waitingSwap = false;
					gui.swapSendButton.setText("Send");
					
					cleanInboundArea();
					cleanOutboundArea();
					
					board.flushMoves();
				}
			} else {
				// We accepted request: remove request words and take offer words
				for (int i = 0; i < s.n; i++) {
					String value = s.requestWords[i];
					
					ArrayList<Word> words = board.getUnprotectedWordsByValue(value);
					
					if(words.size() > 0){
						board.getUnprotectedWords().remove(words.get(0));
					}
				}
				
				for (int i = 0; i < s.n; i++) {
					Word w = new Word(Word.nextId(), s.offerWords[i], Util.parseWordType(s.offerTypes[i]));
					board.getUnprotectedWords().add(w);
				}
				
				System.out.println("Accepted Swap Request successfully");
				
				board.flushMoves();
			}

			setSwapEnabled(true);
			
			return;
		}
	}
}
